#ifndef TERRAIN_FILE_UTILS_H
#define TERRAIN_FILE_UTILS_H

#include <cstdint>
#include <fstream>
#include <cstring>
#include <iostream>
#include <vector>
#include <malloc.h>
#include <omp.h>

namespace RealScan
{

// Terrain file header information
#pragma pack (push, 1)
struct TerrainFileHeader
{
    // File type identifier
    char magic[8];
    
    /* Version number:
     *   higher 8 bits for major
     *   lower 8 bits for minor
     */
    std::int16_t version; 

    // Spatial resolution
    double resolution;

    // 地形矩阵的尺寸
    std::int32_t width;
    std::int32_t height;
};
static_assert(sizeof(TerrainFileHeader) == 26);
#pragma pack (pop)

class TerrainBinary
{
public:
    TerrainBinary() {}    

    ~TerrainBinary() { std::vector<std::int8_t>().swap(m_data); malloc_trim(0); }

    TerrainBinary(std::size_t width, std::size_t height)
        : m_data(width * height)
        , m_width( static_cast<std::int32_t>(width) )
        , m_height( static_cast<std::int32_t>(width) ) {}

    void set(std::int32_t x, std::int32_t y, std::int8_t value)
	{
		if (not inBounds(x, y))
		{
			return;
		}

		m_data[ static_cast<std::size_t>(x) * m_width + y ] = value;
	}

    std::int8_t get(std::int32_t x, std::int32_t y) const
	{
		if (not inBounds(y, x))
		{
			return 0;
		}

		return m_data[static_cast<std::size_t>(x) * m_width + y];
	}
    
    std::int32_t width() const noexcept { return m_width; }

	std::int32_t height() const noexcept { return m_height; }

    double occupancy()
    {
        std::size_t counter = 0;

        #pragma omp parallel
        {
            std::size_t local_counter = 0;

            #pragma omp for nowait
            for (std::int32_t x = 0; x < m_height; ++x)
            {
                for (std::int32_t y = 0; y < m_width; ++y)
                {
                    if (m_data[static_cast<std::size_t>(x) * m_width + y] == 1)
                    {
                        local_counter++;
                    }
                }
            }

            #pragma omp atomic
            counter += local_counter;
        }

        return static_cast<double>(counter) / (m_width * m_height);
    }
    
    bool save(const std::string& path)
    {
        const TerrainFileHeader header
        {
            {'T', 'E', 'R', 'R', 'A', 'I', 'N', '\0'},
            0x0000,
            0.1,
            m_width,
            m_height
        };

        if (std::ofstream ofs{ path, std::ios_base::binary })
		{
            // 写入header信息
			ofs.write(reinterpret_cast<const char*>(&header), sizeof(header));

			std::vector<std::int8_t> line(m_width);

			for (std::int32_t x = 0; x < m_height; ++x)
			{
				for (std::int32_t y = 0; y < m_width; ++y)
				{
					line[y] = m_data[static_cast<std::size_t>(x) * m_width + y];
				}

				ofs.write(reinterpret_cast<const char*>(line.data()), line.size());
			}

			return true;
		}
		else
		{
			return false;
		}
    }

    bool load(const std::string& path)
    {
        // 打开文件
        if (std::ifstream ifs{ path, std::ios_base::binary })
        {
            // 读取文件头
            TerrainFileHeader header;
            ifs.read(reinterpret_cast<char*>(&header), sizeof(header));

            // 验证文件头的有效性
            if (std::strncmp(header.magic, "TERRAIN", sizeof(header.magic)) != 0)
            {
                std::cerr << "Invalid file format!" << std::endl;
                return false;
            }

            // 设置宽度和高度
            m_width  = header.width;
            m_height = header.height;

            m_data.resize(static_cast<std::size_t>(m_width) * m_height);

            // 逐行读取地形数据
            std::vector<std::int8_t> line(static_cast<std::size_t>(m_width));
            for (std::int32_t x = 0; x < m_height; ++x)
            {
                ifs.read(reinterpret_cast<char*>(line.data()), line.size());
                for (std::int32_t y = 0; y < m_width; ++y)
                {
                    m_data[static_cast<std::size_t>(x) * m_width + y] = line[y];
                }
            }

            return true;
        }
        else
        {
            std::cerr << "Failed to open file: " << path << std::endl;
            return false;
        }
    }

private:
    
    std::vector<std::int8_t> m_data;

    std::int32_t m_width = 0, m_height = 0;

    bool inBounds(std::int32_t x, std::int32_t y) const noexcept
	{
		return (0 <= x) && (x < m_height) && (0 <= y) && (y < m_width);
	}
};

} /* RealScan */

#endif /* TERRAIN_FILE_UTILS_H */